/*

  xmunipack - FITS view 


  Copyright © 2009 - 2011 F.Hroch (hroch@physics.muni.cz)

  This file is part of Munipack.

  Munipack is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.
  
  Munipack is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
  
  You should have received a copy of the GNU General Public License
  along with Munipack.  If not, see <http://www.gnu.org/licenses/>.

*/

#include "xmunipack.h"
#include "tune.h"
#include <wx/wx.h>
#include <wx/toolbar.h>
#include <wx/txtstrm.h>
#include <wx/wfstream.h>
#include <wx/utils.h>
#include <wx/wupdlock.h>
#include <wx/combo.h>
#include <wx/treectrl.h>
#include <wx/animate.h>
#include <cfloat>
#include <list>

#define TONE_CURVES "Tone curves"
#define COLOR_PALETTES "Color Palettes"
#define MENU_IMAGE "Image"
#define MENU_HEAD "Head"
#define MENU_GRID "Table"
#define MENU_TUNE "Tune"
#define MENU_TOOLS "Tools"
#define MENU_STRUCT "Structure"


using namespace std;


// ------------------------------------------------------------------------

class MuniSplashing: public wxWindow
{
private:

  wxBitmap logo;

public:
  MuniSplashing(wxWindow *w, const MuniConfig *config):
    wxWindow(w,wxID_ANY),logo(wxBitmap(config->munipack_icon))
  {
    wxLogDebug("Splashing...");

    wxFont bf(*wxNORMAL_FONT);
    bf.SetWeight(wxFONTWEIGHT_BOLD);
    bf.Scale(2.0);

    wxAnimationCtrl *anim = new wxAnimationCtrl(this,wxID_ANY,config->throbber);
    wxBoxSizer *topsizer = new wxBoxSizer(wxVERTICAL);
    topsizer->AddStretchSpacer();
    topsizer->Add(new wxStaticBitmap(this,wxID_ANY,logo),wxSizerFlags().Center().Border());
    wxStaticText *label = new wxStaticText(this,wxID_ANY,"Munipack");
    label->SetFont(bf);
    label->SetForegroundColour(wxColour(128,128,128));
    topsizer->Add(label,wxSizerFlags().Center().DoubleBorder());
    topsizer->Add(anim,wxSizerFlags().Center().Border());
    topsizer->AddStretchSpacer();
    SetSizer(topsizer);

    anim->Play();
  }
};


// ------------------------------------------------------------------------

class xTreeItemData: public wxTreeItemData
{
public:
  xTreeItemData(const int n): index(n) {}

  int index;
};


// ------------------------------------------------------------


class MuniStructtree: public wxTreeCtrl, public wxComboPopup
{
private:

  // an initial code fork from wxWidgets docs, chapter:
  //      Setting Custom Popup for wxComboCtrl

protected:

    wxTreeItemId        m_value; // current item index
    wxTreeItemId        m_itemHere; // hot item in popup



  virtual void Init() {

    Bind(wxEVT_MOTION,&MuniStructtree::OnMouseMove,this);
    Bind(wxEVT_LEFT_DOWN,&MuniStructtree::OnMouseClick,this);

  }

  // Create popup control
  virtual bool Create(wxWindow* parent) {

    return wxTreeCtrl::Create(parent,wxID_ANY,wxPoint(0,0),wxDefaultSize,
     (wxTR_DEFAULT_STYLE | wxTR_HAS_BUTTONS | wxTR_HIDE_ROOT | wxTR_FULL_ROW_HIGHLIGHT) & ~wxTR_NO_LINES);
  }
  

  // Return pointer to the created control
  virtual wxWindow *GetControl() { return this; }
  
    virtual void OnShow()
    {
        // make sure selected item is visible
        if ( m_value.IsOk() )
            EnsureVisible(m_value);
    }

    virtual wxSize GetAdjustedSize( int minWidth,
                                    int WXUNUSED(prefHeight),
                                    int maxHeight )
    {
        return wxSize(wxMax(300,minWidth),wxMin(250,maxHeight));
    }

    // Needed by SetStringValue
    wxTreeItemId FindItemByText( wxTreeItemId parent, const wxString& text )
    {
        wxTreeItemIdValue cookie;
        wxTreeItemId child = GetFirstChild(parent,cookie);
        while ( child.IsOk() )
        {
            if ( GetItemText(child) == text )
            {
                return child;
            }
            if ( ItemHasChildren(child) )
            {
                wxTreeItemId found = FindItemByText(child,text);
                if ( found.IsOk() )
                    return found;
            }
            child = GetNextChild(parent,cookie);
        }
        return wxTreeItemId();
    }

    virtual void SetStringValue( const wxString& s )
    {
        wxTreeItemId root = GetRootItem();
        if ( !root.IsOk() )
            return;

        wxTreeItemId found = FindItemByText(root,s);
        if ( found.IsOk() )
        {
            m_value = m_itemHere = found;
            wxTreeCtrl::SelectItem(found);
        }
    }

    virtual wxString GetStringValue() const
    {
        if ( m_value.IsOk() )
            return wxTreeCtrl::GetItemText(m_value);
        return wxEmptyString;
    }

    //
    // Popup event handlers
    //

    // Mouse hot-tracking
    void OnMouseMove(wxMouseEvent& event)
    {
        int resFlags;
        wxTreeItemId itemHere = HitTest(event.GetPosition(),resFlags);
        if ( itemHere.IsOk() && 
	     ((resFlags & wxTREE_HITTEST_ONITEMLABEL) || 
	      (resFlags & wxTREE_HITTEST_ONITEMICON) ||
	      (resFlags & wxTREE_HITTEST_ONITEMSTATEICON) ||
	      (resFlags & wxTREE_HITTEST_ONITEMBUTTON) ||
	      (resFlags & wxTREE_HITTEST_ONITEMINDENT) ||
	      (resFlags & wxTREE_HITTEST_ONITEMRIGHT) ) )
        {
            wxTreeCtrl::SelectItem(itemHere,true);
            m_itemHere = itemHere;
        }
        event.Skip();
    }

    // On mouse left, set the value and close the popup
    void OnMouseClick(wxMouseEvent& event)
    {
        int resFlags;
        wxTreeItemId itemHere = HitTest(event.GetPosition(),resFlags);
        if ( itemHere.IsOk() && 
	     ((resFlags & wxTREE_HITTEST_ONITEMLABEL) ||
	      (resFlags & wxTREE_HITTEST_ONITEMICON) ||
	      (resFlags & wxTREE_HITTEST_ONITEMSTATEICON) ||
	      (resFlags & wxTREE_HITTEST_ONITEMBUTTON) ||
	      (resFlags & wxTREE_HITTEST_ONITEMINDENT) ||
	      (resFlags & wxTREE_HITTEST_ONITEMRIGHT) ) )
        {
            m_itemHere = itemHere;
            m_value = itemHere;
            Dismiss();

	    wxTreeItemId item = GetFocusedItem();
	    if( item.IsOk() ) {
	      xTreeItemData *data = dynamic_cast<xTreeItemData *>(GetItemData(item));
	      wxASSERT(data);
	      
	      MuniHduEvent ev(EVT_HDU);
	      ev.hdu = data->index;
	      wxQueueEvent(GetGrandParent(),ev.Clone());
	    }

        }
        event.Skip();
    }

  void PaintComboControl(wxDC& dc, const wxRect& rect)
  {
    wxTreeItemId item = GetFocusedItem();
    if( item.IsOk() ) {

      wxString label(GetItemText(item));

      wxImageList *ilist = GetImageList();
      int index = GetItemImage(item);
      if( index > -1 ) {
	wxBitmap icon(ilist->GetBitmap(index));
	dc.DrawLabel(label,icon,rect,wxALIGN_CENTER|wxALIGN_CENTER_VERTICAL);
      }
      else
	dc.DrawLabel(label,rect,wxALIGN_CENTER|wxALIGN_CENTER_VERTICAL);
    }
    else
      dc.DrawLabel("-",rect,wxALIGN_CENTER|wxALIGN_CENTER_VERTICAL);

  }

public:

  void SetMeta(const FitsMeta& meta) {
    
    DeleteAllItems();

    wxFont fn(*wxNORMAL_FONT);
    int ssize = 2*(fn.GetPointSize()+3);
    wxImageList *icons = new wxImageList(ssize, ssize, true);
    for(size_t n = 0; n < meta.HduCount(); n++ ) {
      const FitsMetaHdu hdu = meta.Hdu(n);
      icons->Add(wxBitmap(MuniIcon::ListIcon(hdu.GetIcon(),ssize)));
    }
    icons->Add(wxBitmap(MuniIcon::ListIcon(meta.GetIcon(),ssize)));
    AssignImageList(icons);
    
    wxTreeItemId root = AddRoot("<root>");

    wxTreeItemId hub = AppendItem(root,meta.GetName(),meta.HduCount(),-1,new xTreeItemData(-1));
    for(size_t n = 0; n < meta.HduCount(); n++ ) {
      const FitsMetaHdu hdu = meta.Hdu(n);
      wxString label = hdu.GetControlLabel();
      wxTreeItemId hdus = AppendItem(hub,label,n,-1,new xTreeItemData(n));
    }
    Expand(hub);
  }

  void SetHdu(int hdu)
  {
    wxTreeItemId root = GetRootItem();

    wxTreeItemIdValue cookie;
    wxTreeItemId hub = GetFirstChild(root,cookie);
    while ( hub.IsOk() ) {
      
      xTreeItemData *data = dynamic_cast<xTreeItemData *>(GetItemData(hub));
      wxASSERT(data);
      if( data->index == hdu ) {
	SelectItem(hub);	
	return;
      }
      else {

	wxTreeItemIdValue cookies;
	wxTreeItemId child = GetFirstChild(hub,cookies);
	while ( child.IsOk() ) {

	  xTreeItemData *data = dynamic_cast<xTreeItemData *>(GetItemData(child));
	  wxASSERT(data);
	  if( data->index == hdu ) {
	    SelectItem(child);
	    return;
	  }
	  
	  child = GetNextChild(hub,cookies);
        }
      }
      hub = GetNextChild(root,cookie);
    }
  }
};


// ------------------------------------------------------------------------


MuniView::MuniView(wxWindow *w, MuniConfig *c):
  wxFrame(w, wxID_ANY,"untitled", wxDefaultPosition, c->view_size), loader(0),
  config(c), hdusel(0), ntools(0), place(0), onreset(false),
  coloring(0),
  console(new wxLogWindow(this,"Logs",false))
  /*shell(0),*/
{
  SetIcon(config->munipack_icon);

  // menus
  menuFile = new wxMenu;
  menuFile->Append(wxID_NEW);
  
  //  menuFile->Append(ID_NEW_VIEW, wxT("&New View..."));
  //  menuFile->Append(ID_NEW_BROWSER,wxT("&New Browser..."));
  menuFile->Append(wxID_OPEN);
  wxMenu *menuFileCreate = new wxMenu;
  menuFileCreate->Append(ID_COLORING,"Color Image...");
  //  menuFileCreate->Append(wxID_ANY,"Image server ...");
  //  menuFileCreate->Append(wxID_ANY,"Synthetic image...");
  menuFile->AppendSubMenu(menuFileCreate,"Create");
  //  menuFile->AppendSubMenu(ID_VOOPEN,"Virtual Observatory");
  menuFile->AppendSeparator();
  menuFile->Append(wxID_SAVE);
  menuFile->Append(ID_EXPORT,"Export As...");
#ifdef __WXMAC__
  menuFile->Append(wxID_CLOSE);
#endif
  menuFile->AppendSeparator();
  menuFile->Append(wxID_PROPERTIES);
  menuFile->AppendSeparator();
  menuFile->Append(ID_PAGE_SETUP,"Page Setup");
  menuFile->Append(wxID_PREVIEW);
  menuFile->Append(wxID_PRINT);
#ifndef __WXMAC__
  menuFile->AppendSeparator();
  menuFile->Append(wxID_CLOSE);
#endif
#ifdef __WXMAC__
  menuFile->AppendSeparator();
  menuFile->Append(wxID_EXIT);
#endif

  wxMenu *menuEdit =  new wxMenu;
  menuEdit->Append(wxID_UNDO);
  menuEdit->Append(wxID_CUT);
  menuEdit->Append(wxID_COPY);
  menuEdit->Append(wxID_PASTE);
#ifndef __WXMAC__
  menuEdit->AppendSeparator();
  menuEdit->Append(wxID_PREFERENCES);
#endif


  menuView = new wxMenu;
  menuView->Append(ID_FULLSCREEN,"&Fullscreen",
		   "Enable fullscreen image display");
  menuView->AppendSeparator();
  menuView->AppendCheckItem(ID_TOOLBAR,"&Show Tool Bar",
    "Change visibility of toolbar (shorthand buttons with icons on top)");
  menuView->Check(ID_TOOLBAR,config->view_tbar);

  /*
  wxMenu *menuGo = new wxMenu;
  menuGo->Append(wxID_BACKWARD,"Previous\tCtrl+Left");
  menuGo->Append(wxID_FORWARD,"Next\tCtrl+Right");
  */

  wxMenu *menuHelp = new wxMenu;
  menuHelp->Append(wxID_HELP,wxEmptyString,"A web connection to " HOMEPAGE);
  //  menuHelp->AppendCheckItem(ID_LOG,"&Log");
  menuHelp->Append(ID_LOG,"Log");
  menuHelp->Append(ID_BUG,"Report a Bug ...",
		   "Launch your default browser to Munipack's issues.");
  menuHelp->Append(wxID_ABOUT);

  menuWin = new wxMenu;

  wxMenuBar *menuBar = new wxMenuBar;
  menuBar->Append(menuFile,"&File");
  menuBar->Append(menuEdit,"&Edit");
  menuBar->Append(menuView,"&View");
  //  menuBar->Append(menuGo,"&Go");
  menuBar->Append(menuWin,MENU_STRUCT);
  menuBar->Append(menuHelp,"&Help");
  SetMenuBar(menuBar);


  // toolbars
  wxToolBar *tbar = CreateToolBar(wxTB_HORIZONTAL|wxTB_TEXT);
  tbar->AddStretchableSpace();

  MuniArtIcons ico(wxART_TOOLBAR,wxSize(22,22));

  tbar->AddTool(ID_INFO,"Info",ico.Icon(wxART_INFORMATION),"Show FITS header");
  tbar->EnableTool(ID_INFO,false);

  tbar->AddTool(wxID_HOME,"Structure",ico.Icon("gtk-home"),"Show structure of this FITS file");
  tbar->EnableTool(wxID_HOME,false);

  MuniStructtree* popupCtrl = new MuniStructtree();
  structtree = new wxComboCtrl(tbar,wxID_ANY,wxEmptyString,wxDefaultPosition,wxDefaultSize,
			       wxCB_READONLY|wxCC_STD_BUTTON);
  structtree->SetPopupControl(popupCtrl);
  tbar->AddControl(structtree);

  tbar->Realize();
  tbar->Show(config->view_tbar);
  SetToolBar(tbar);


  // workplace
  place = new MuniSplashing(this,config);

  wxBoxSizer *topsizer = new wxBoxSizer(wxVERTICAL);
  topsizer->Add(place,wxSizerFlags(1).Expand());
  SetSizer(topsizer);

  //  menuFile->Enable(wxID_SAVE,false);
  //  menuFile->Enable(ID_EXPORT,false);
  menuFile->Enable(ID_PAGE_SETUP,false);
  menuFile->Enable(wxID_PREVIEW,false);
  menuFile->Enable(wxID_PRINT,false);

  menuEdit->Enable(wxID_UNDO,false);
  menuEdit->Enable(wxID_CUT,false);
  menuEdit->Enable(wxID_COPY,false);
  menuEdit->Enable(wxID_PASTE,false);
  //  menuEdit->Enable(wxID_PREFERENCES,false);

  menuView->Enable(ID_FULLSCREEN,false);
  menuFile->Enable(wxID_PROPERTIES,false);

  //  menuGo->Enable(wxID_FORWARD,false);
  //  menuGo->Enable(wxID_BACKWARD,false);

  Bind(wxEVT_CLOSE_WINDOW,&MuniView::OnClose,this);
  //  Bind(wxEVT_SIZE,&MuniView::OnSize,this);
  //  Bind(wxEVT_ENTER_WINDOW,&MuniView::OnEnterWin,this);
  //  Connect(wxEVT_LEAVE_WINDOW,wxMouseEventHandler(MuniView::OnLeaveWin));
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::FileClose,this,wxID_CLOSE);
  Bind(wxEVT_COMMAND_MENU_SELECTED, &MuniView::FileClose,this,wxID_EXIT);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::NewView,this,wxID_NEW);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::FileOpen,this,wxID_OPEN);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::FileSave,this,wxID_SAVE);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::FileExport,this,ID_EXPORT);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::FileProperties,this,wxID_PROPERTIES);
  //  Connect(wxID_PREFERENCES,wxEVT_COMMAND_MENU_SELECTED,
  //	  wxCommandEventHandler(MuniView::OnPreferences));
  //  Connect(wxID_BACKWARD,wxEVT_COMMAND_MENU_SELECTED,
  //	  wxCommandEventHandler(MuniView::OnSelectFrame));
  //  Connect(wxID_FORWARD,wxEVT_COMMAND_MENU_SELECTED,
  //	  wxCommandEventHandler(MuniView::OnSelectFrame));
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::ShowToolbar,this,ID_TOOLBAR);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnFullScreen,this,ID_FULLSCREEN);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::Coloring,this,ID_COLORING);
  //  Bind(EVT_SHELL,&MuniView::OnShell,this);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::HelpGuide,this,wxID_HELP);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::ViewLog,this,ID_LOG);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::HelpAbout,this,wxID_ABOUT);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::HelpBug,this,ID_BUG);

  Bind(EVT_HDU,&MuniView::OnResumeHdu,this);
  Bind(EVT_CONFIG_UPDATED,&MuniView::OnConfigUpdated,this);
  //  Connect(EVT_CONFIG_UPDATED,wxCommandEventHandler(MuniView::OnConfigUpdated));
  Bind(EVT_FITS_OPEN,&MuniView::OnLoadFinish,this,ID_LOADER);
  //  Bind(EVT_FILELOAD,&MuniView::OnFileLoad,this);

}

MuniView::~MuniView() {}


void MuniView::OnClose(wxCloseEvent& event)
{
  if( event.CanVeto() && /*fits.IsModified()*/ ! backup.IsEmpty() ) {
    wxMessageDialog dialog(this,"Do you really want to leave this window ?",
			   "Confirm Close",wxICON_EXCLAMATION | wxYES_NO | wxNO_DEFAULT);
    dialog.SetExtendedMessage("Your changes in \""+fits.GetName()+
			      "\" will be lost if you will continue in closing now.");
    if ( dialog.ShowModal() != wxID_YES ) {
      event.Veto();
      return;
    }
  }

  StopLoading();

  if( ! backup.IsEmpty() ) {
    wxBusyCursor wait;
    for(size_t i = 0; i < backup.GetCount(); i++)
      wxRemoveFile(backup[i]);
  }

  if( GetParent() )
    wxQueueEvent(GetParent(),event.Clone());

  config->view_tbar = GetToolBar()->IsShown();
  config->view_size = GetSize();

  Destroy();

}

void MuniView::ViewLog(wxCommandEvent& event)
{
  wxASSERT(console);
  console->Show();
}


void MuniView::OnUpdateFit(wxUpdateUIEvent& event)
{
  wxTopLevelWindow *twin = static_cast<wxTopLevelWindow *>(this);
  if( !twin || twin->IsFullScreen()) return;

  MuniDisplay *display = dynamic_cast<MuniDisplay *>(place);
  if( display ) 
    event.Check(display->GetFit());
}

void MuniView::OnUpdateNFit(wxUpdateUIEvent& event)
{
  wxTopLevelWindow *twin = static_cast<wxTopLevelWindow *>(this);
  if( !twin || twin->IsFullScreen()) return;

  MuniDisplay *display = dynamic_cast<MuniDisplay *>(place);
  if( display ) 
    event.Enable(!display->GetFit());
}

void MuniView::OnUpdateInfos(wxUpdateUIEvent& event)
{
  event.Check(GetToolBar()->GetToolState(ID_INFO));
}

void MuniView::StopLoading()
{
  {
    wxCriticalSectionLocker enter(loaderCS);
    if( loader ) {
      loader->Delete();
    }
  }

  while(true) {
    {
       wxCriticalSectionLocker enter(loaderCS);
       if( ! loader ) break;
    }
    ::wxMilliSleep(1);
  }
}

void MuniView::LoadMeta(const FitsMeta& m)
{
  wxLogDebug("Loading meta .. "+m.GetFullPath());
  SetMeta(m);

  LoadFile(m.GetFullPath());
}

void MuniView::LoadFile(const wxString& filename)
{
  fitsname = filename;
  backup.Clear();
  LoadStart(filename);
}

void MuniView::ReloadFile()
{
  if( fits.Status() ) {
    LoadStart(backup.Last());
  }
}


void MuniView::LoadStart(const wxString& filename)
{
  wxLogDebug("Loading .. "+filename);

  StopLoading();

  //  wxASSERT(loader);
  loader = new FitsOpen(this,filename);
  wxThreadError code = loader->Create();
  wxASSERT(code == wxTHREAD_NO_ERROR);
  loader->Run();

  //  wxBeginBusyCursor();

  //  Clear(); // ???

}

void MuniView::OnLoadFinish(FitsOpenEvent& event)
{
  wxLogDebug("MuniView::OnLoadFinish");

  wxString oldname = fits.GetName();

  fits = event.fits;

  if( fits.Status() ) {

    if( ! meta.IsOk() ) {
      MuniIcon micon(fits,config);
      meta = FitsMeta(fits,micon.GetIcon(),micon.GetList());
      SetMeta(meta);
    } 

    wxASSERT(meta.IsOk());
    wxASSERT(fits.HduCount() > 0);

    hdusel = EstimHduSel(meta);
    HduSelect(hdusel);
    MenuSelect(hdusel);
    TreeSelect(hdusel);

    //wxString title = fitsname;
    wxString title = fits.GetName();
    if( ! backup.IsEmpty() && fits.GetFullPath() == backup.Last() )
      title = oldname + "*";
    SetTitle(title);

  }
  else
    wxLogError("Failed to load: `"+event.filename+"'");

}




void MuniView::Clear()
{
  meta.Clear();
  fits.Clear();

  // disable menus ?
  //  hists.clear();
  viewid.clear();
}


void MuniView::SetMeta(const FitsMeta& m)
{
  wxASSERT(m.IsOk() && menuWin);

  meta = m;

  menuFile->Enable(wxID_PROPERTIES,true);

  menuWin = new wxMenu;

  wxMenuBar *menuBar = GetMenuBar();
  wxMenu *oldmenu = menuBar->Replace(menuBar->FindMenu(MENU_STRUCT),
				     menuWin,MENU_STRUCT);
  wxASSERT(oldmenu);

  while( oldmenu->GetMenuItemCount() > 0 ) {
    int i = oldmenu->GetMenuItemCount() - 1;
    wxMenuItem *item = oldmenu->FindItemByPosition(i);
    wxASSERT(item);
    int id = item->GetId();
    Unbind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnMenuStructure,this,id);
    oldmenu->Destroy(id);
  }
  delete oldmenu;
  viewid.clear();


  menuWin->AppendRadioItem(wxID_HOME,"Resume\tAlt+Home");
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnMenuStructure,this,wxID_HOME);

  // create HDU menu
  for(size_t k = 0; k < meta.HduCount(); k++ ) {

    wxString label = meta.Hdu(k).GetControlLabel();

    if( k < 9 ) {
      wxString l;
      l.Printf("\tAlt+%d",(int) k+1);
      label += l;
    }
    wxMenuItem* item = menuWin->AppendRadioItem(wxID_ANY,label);
    wxASSERT(item);
    int id = item->GetId();
    Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnMenuStructure,this,id);
    viewid.push_back(id);
  }

  // set Tree structure
  MuniStructtree *tree = static_cast<MuniStructtree *>(structtree->GetPopupControl());
  wxASSERT(tree);
  tree->SetMeta(meta);

}

int MuniView::EstimHduSel(const FitsMeta& meta)
{
  wxASSERT(meta.HduCount() > 0);

  // we are directly selecting image at these cases:
  // * there is an image and MUNIPACK extension
  // * the first primary extension is an image

  if( meta.Hdu(0).Type() == HDU_IMAGE )
    return 0;
  else
    return -1;
}


// void MuniView::EstimHistos(const FitsFile& fits)
// {
//   double xmin,xmax;
//   bool color = fits.Type() == FITS_COLOR;

  
//   if( color ) {

//     /*
//     FitsImage fimage(fits,-1);
//     FitsImage fs = fimage.Scale(0.1);
//     FitsArray X = fs.Item(0);
//     FitsArray Y = fs.Item(1);
//     FitsArray Z = fs.Item(2);
//     int npix = X.Width()*X.Height();
//     FitsColor color;
//     for(int i = 0; i < npix; i++) {
//       float L,u,v;
//       color.XYZ_Luv(X.PIxel(i),Y.Pixel(i),Z.Pixel(i),1.0,L,u,v);
//     }
//     */

//     FitsArrayStat array(fits.Hdu(2));
//     /*
//     xmin = max(array.Med() - 2.0*array.Mad(),0.0);
//     xmax = array.Med() + 3.0*array.Mad();
//     xmin = 0.0;
//     xmax = 7.0*500.0;
//     */
//     xmin = array.Med() - 10.0*array.Mad();
//     xmax = array.Med() + 20.0*array.Mad();
//     // convrt to XYZ, Luv
//     //    hits = FitsHisto(
//     hist = FitsHisto(fits.Hdu(2),xmin,xmax);
//   } 
//   else if( fits.Type() == FITS_GRAY ) {
//     FitsArrayStat array(fits.Hdu(0));
//     xmin = array.Med() - 5.0*array.Mad();
//     xmax = array.Med() + 7.0*array.Mad();
//     hist = FitsHisto(fits.Hdu(0),xmin,xmax);
//   }
//   else
//     hist = FitsHisto();

//   hists.clear();
//   for(size_t i = 0; i < fits.HduCount(); i++) {
//     if( fits.Hdu(i).Type() == HDU_IMAGE ) {
//       //      if( ! color ) {
// 	FitsArrayStat array(fits.Hdu(i));
// 	xmin = array.Med() - 5.0*array.Mad();
// 	xmax = array.Med() + 7.0*array.Mad();
// 	//      }
//       hists.push_back(FitsHisto(fits.Hdu(i),xmin,xmax));
//     }
//     else {
//       hists.push_back(FitsHisto());
//     }
//   }
// }


void MuniView::ShowToolbar(wxCommandEvent& event)
{
  GetToolBar()->Show(event.IsChecked());
  Layout();
}

void MuniView::FileClose(wxCommandEvent& WXUNUSED(event))
{
  Close();
}

void MuniView::FileOpen(wxCommandEvent& WXUNUSED(event))
{
  wxFileDialog select(this,"Choose a file",wxEmptyString,wxEmptyString,
		      "FITS files "+config->dirmask+")|"+
		      config->dirmask+"| All files (*)|*",
		      wxFD_FILE_MUST_EXIST|wxFD_CHANGE_DIR);
  if (select.ShowModal() == wxID_OK ) {
    meta = FitsMeta();
    LoadFile(select.GetPath());
  }
}

void MuniView::FileSave(wxCommandEvent& WXUNUSED(event))
{
  wxFileDialog select(this,"Choose a file",wxEmptyString,wxEmptyString,
		      "FITS files "+config->dirmask+")|"+
		      config->dirmask+"| All files (*)|*",
		      wxFD_SAVE|wxFD_OVERWRITE_PROMPT|wxFD_CHANGE_DIR);

  if (select.ShowModal() == wxID_OK ) {
    wxBusyCursor wait;
    
    bool s = fits.Save("!"+select.GetPath());
    if( s ) {
      fitsname = fits.GetName();  
      SetTitle(fitsname);
      backup.Clear();
    }
  }


  // if( false /*HduHead()*/ ) {

  //   wxFileDialog select(this,"Choose text file",wxEmptyString,wxEmptyString,
  // 			"Text files (*.txt)|*.txt| All files (*)|*",
  // 			wxFD_SAVE|wxFD_OVERWRITE_PROMPT|wxFD_CHANGE_DIR);

  //   //    if (select.ShowModal() == wxID_OK )
  //     //      SaveAsText(select.GetPath());
  // }
  // else
  //   wxLogDebug("Not implemented yet.");
}

void MuniView::FileExport(wxCommandEvent& WXUNUSED(event))
{
  // exporting images
  if( dynamic_cast<MuniDisplay *>(place) ) {

    wxFileDialog select(this,"Export As",wxEmptyString,wxEmptyString,
			"PNG files (*.png)|*.png|JPEG files (*.jpg)|*.jpg|TIFF files (*.tif)|*.tif|PNM files (*.pnm)|*.pnm",
			wxFD_SAVE|wxFD_OVERWRITE_PROMPT|wxFD_CHANGE_DIR);

    if (select.ShowModal() == wxID_OK ) {

      /*
      wxProgressDialog dialog(wxT("Export of Image"),wxT("Exporting ")+
			      select.GetFilename()+wxT(" ... "),100,this,
			      wxPD_APP_MODAL|wxPD_AUTO_HIDE);
      dialog.Pulse();
      */
      wxBusyCursor wait;
      wxImage image = dynamic_cast<MuniDisplay *>(place)->GetImage();
      image.SaveFile(select.GetPath());
    }
  }

  // exporting tables
  else if( dynamic_cast<MuniGrid *>(place) ) {

    wxFileDialog select(this,"Export As",wxEmptyString,wxEmptyString,
			"TEXT files (*.txt)|*.txt",
			wxFD_SAVE|wxFD_OVERWRITE_PROMPT|wxFD_CHANGE_DIR);

    if (select.ShowModal() == wxID_OK ) {

      /*
      wxProgressDialog dialog(wxT("Export of Table"),wxT("Exporting ")+
			      select.GetFilename()+wxT(" ... "),100,this,
			      wxPD_APP_MODAL|wxPD_AUTO_HIDE);
      dialog.Pulse();
      */

      wxBusyCursor wait;

      wxFileOutputStream output(select.GetPath());
      wxTextOutputStream cout(output);

      const FitsTable table(fits.Hdu(/*HduSel()*/hdusel));
      cout << "#";
      for(int i = 0; i < table.Width(); i++) {
	wxString key;
	key.Printf("TTYPE%d",(int) i+1);
	cout << " " << table.GetKey(key);
      }
      cout << endl;

      for(int j = 0; j < table.Height(); j++) {
	cout << j;
	for(int i = 0; i < table.Width(); i++)
	  cout << " " << table.Pixel_str(i,j) ;
	cout << endl;
	//	if( j % 100 == 0) dialog.Pulse();      
      }
    }
  }

  // exporting headers
  else if( dynamic_cast<MuniHead *>(place) ) {

    wxFileDialog select(this,"Export As",wxEmptyString,wxEmptyString,
			"TEXT files (*.txt)|*.txt",
			wxFD_SAVE|wxFD_OVERWRITE_PROMPT|wxFD_CHANGE_DIR);

    if (select.ShowModal() == wxID_OK ) {

      /*
      wxProgressDialog dialog(wxT("Export of Header"),wxT("Exporting ")+
			      select.GetFilename()+wxT(" ... "),100,this,
			      wxPD_APP_MODAL|wxPD_AUTO_HIDE);
      dialog.Pulse();
      */

      wxBusyCursor wait;

      wxFileOutputStream output(select.GetPath());
      wxTextOutputStream cout(output);
      
      const FitsHdu head = fits.Hdu(hdusel);
      for(size_t i = 0; i < head.GetCount(); i++)
	cout << head.Item(i) << endl;
    }
  }

  else
    wxFAIL_MSG("----- WARNING: Unreachable code.");
    
}

/*
void MuniView::SaveAsText(wxString filename)
{
  wxFileOutputStream output(filename);
  wxTextOutputStream cout(output);

  const FitsHdu head = fits.Hdu(HduSel());
  for(size_t i = 0; i < head.GetCount(); i++)
    cout << head.Item(i) << endl;
}
*/

void MuniView::FileProperties(wxCommandEvent& WXUNUSED(event))
{
  MuniFileProperties *w = new MuniFileProperties(this,meta,config);
  w->Show();
}

void MuniView::OnFileLoad(wxCommandEvent& event)
{
  //  wxLogDebug(event.GetString());
  LoadFile(event.GetString());
}


void MuniView::OnPreferences(wxCommandEvent& WXUNUSED(event))
{
  MuniPreferences *w = new MuniPreferences(this,config);
  w->Show();
}

void MuniView::OnSelectFrame(wxCommandEvent& event)
{
  if( GetParent() )
    wxQueueEvent(GetParent(),event.Clone());
}

void MuniView::OnCycleZoom(wxCommandEvent& event)
{
  wxASSERT(dynamic_cast<MuniDisplay *>(place));

  double z = dynamic_cast<MuniDisplay *>(place)->GetZoom();
  double zoom = -1.0;

  switch(event.GetId()) {
  case wxID_ZOOM_IN: zoom = FitsZoom::FindGreater(z); break;
  case wxID_ZOOM_OUT: zoom = FitsZoom::FindSmaller(z); break;
  }

  MuniTuneEvent ev(EVT_TUNE,ID_ZOOM_SCALE);
  ev.x = zoom;
  if( event.GetId() == wxID_ZOOM_FIT )
    ev.fit = event.IsChecked();
  else
    ev.fit = false;

  wxQueueEvent(place,ev.Clone());
}

void MuniView::OnMenuZoom(wxCommandEvent& event)
{
  wxASSERT(place);

  double zoom = 1.0;

  wxString entry;  
  switch(event.GetId()) {
  case wxID_ZOOM_FIT: zoom = -1.0; break;
  case wxID_ZOOM_100: zoom = 1.0; break;
  }
 
  MuniTuneEvent ev(EVT_TUNE,ID_ZOOM_SCALE);
  ev.x = zoom;
  if( event.GetId() == wxID_ZOOM_FIT )
    ev.fit = event.IsChecked();
  else
    ev.fit = false;

  wxQueueEvent(place,ev.Clone());
}

void MuniView::OnMenuItt(wxCommandEvent& event)
{ 
  wxMenu *menuTune = GetMenuBar()->GetMenu(GetMenuBar()->FindMenu(MENU_TUNE));
  wxMenuItem *item = menuTune->FindItem(menuTune->FindItem(TONE_CURVES));
  wxMenu *menuProfile= item->GetSubMenu();
  wxASSERT(place && menuTune && menuProfile && 
	   menuProfile->FindItem(event.GetId()));

  MuniTuneEvent ev(EVT_TUNE,ID_ITT_TYPE);
  wxMenuItem *sitem = menuProfile->FindItem(event.GetId());
  ev.SetString(sitem->GetItemLabelText());
  wxQueueEvent(place,ev.Clone());
}

void MuniView::OnCycleItt(wxCommandEvent& event)
{ 
  wxMenu *menuTune = GetMenuBar()->GetMenu(GetMenuBar()->FindMenu(MENU_TUNE));
  wxMenuItem *item = menuTune->FindItem(menuTune->FindItem(TONE_CURVES));
  wxMenu *menuProfile= item->GetSubMenu();
  wxASSERT(place && menuTune && menuProfile);

  size_t nmenu = menuProfile->GetMenuItemCount();
  wxString label;
  int itemid = -1;
  for(size_t i = 0; i < nmenu; i++) {
    wxMenuItem *item = menuProfile->FindItemByPosition(i);
    if( item && item->IsChecked() ) {
      wxMenuItem *it = menuProfile->FindItemByPosition(i+1 < nmenu ? i+1 : 0);
      if( it ) {
	label = it->GetItemLabelText();
	itemid = it->GetId();
      }
    }
  }
  wxASSERT(!label.IsEmpty());

  menuProfile->Check(itemid,true);

  MuniTuneEvent ev(EVT_TUNE,ID_ITT_TYPE);
  ev.SetString(label);
  wxQueueEvent(place,ev.Clone());
}

void MuniView::OnMenuPal(wxCommandEvent& event)
{ 
  wxMenu *menuTune = GetMenuBar()->GetMenu(GetMenuBar()->FindMenu(MENU_TUNE));
  wxMenuItem *item = menuTune->FindItem(menuTune->FindItem(COLOR_PALETTES));
  wxMenu *menuPalette= item->GetSubMenu();
  wxASSERT(place && menuTune && menuPalette && 
	   menuPalette->FindItem(event.GetId()));

  MuniTuneEvent ev(EVT_TUNE,ID_PALETTE_TYPE);
  wxMenuItem *sitem = menuPalette->FindItem(event.GetId());
  ev.SetString(sitem->GetItemLabelText());
  wxQueueEvent(place,ev.Clone());
}

void MuniView::OnCyclePal(wxCommandEvent& event)
{ 
  wxMenu *menuTune = GetMenuBar()->GetMenu(GetMenuBar()->FindMenu(MENU_TUNE));
  wxMenuItem *item = menuTune->FindItem(menuTune->FindItem(COLOR_PALETTES));
  wxMenu *menuPalette= item->GetSubMenu();
  wxASSERT(place && menuTune && menuPalette);

  size_t nmenu = menuPalette->GetMenuItemCount();
  wxString label;
  int itemid = -1;
  for(size_t i = 0; i < nmenu; i++) {
    wxMenuItem *item = menuPalette->FindItemByPosition(i);
    if( item && item->IsChecked() ) {
      wxMenuItem *it = menuPalette->FindItemByPosition(i+1 < nmenu ? i+1 : 0);
      if( it ) {
	label = it->GetItemLabelText();
	itemid = it->GetId();
      }
    }
  }
  wxASSERT(!label.IsEmpty() );

  menuPalette->Check(itemid,true);  

  MuniTuneEvent ev(EVT_TUNE,ID_PALETTE_TYPE);
  ev.SetString(label);
  wxQueueEvent(place,ev.Clone());
}

void MuniView::OnMenuNegative(wxCommandEvent& event)
{ 
  wxASSERT(place);

  MuniTuneEvent ev(EVT_TUNE,ID_PALETTE_NEGATIVE);
  ev.SetInt(event.IsChecked());
  wxQueueEvent(place,ev.Clone());
}



void MuniView::OnTuneFine(MuniTuneEvent& event)
{
  wxMenu *menuTune = GetMenuBar()->GetMenu(GetMenuBar()->FindMenu(MENU_TUNE));
  wxMenuItem *item = menuTune->FindItem(menuTune->FindItem(COLOR_PALETTES));
  wxMenu *menuPalette = 0;
  if( item ) 
    menuPalette = item->GetSubMenu();

  item = menuTune->FindItem(menuTune->FindItem(TONE_CURVES));
  wxMenu *menuProfile = item->GetSubMenu();

  wxASSERT(place && menuTune && menuProfile);

  int itemid;

  switch(event.GetId()) {
  case ID_ITT_TYPE: 
    itemid = menuProfile->FindItem(event.GetString());
    wxASSERT(itemid != wxNOT_FOUND);
    menuProfile->Check(itemid,true);
    break;
  case ID_PALETTE_TYPE: 
    if( menuPalette ) {
      itemid = menuPalette->FindItem(event.GetString());
      wxASSERT(itemid != wxNOT_FOUND);
      menuPalette->Check(itemid,true);
    }
    break;
  case ID_PALETTE_NEGATIVE:
    if( menuPalette )
      menuTune->Check(ID_MENU_NEGATIVE,event.GetInt());
    break;
  }

  onreset = event.GetId() == ID_RESET;

  wxQueueEvent(place,event.Clone());
}


// void MuniView::NewBrowser(wxCommandEvent& WXUNUSED(event))
// {
//   MuniBrowser *b = new MuniBrowser(config);
//   b->Show();
// }

void MuniView::NewView(wxCommandEvent& WXUNUSED(event))
{
  MuniView *w = new MuniView(this,config);
  w->Show();
}

void MuniView::OnFullScreen(wxCommandEvent& WXUNUSED(event))
{
  MuniDisplay *display = dynamic_cast<MuniDisplay*>(place);
  if( ! display ) return;

  if( IsFullScreen() ) {
    GetToolBar()->Show(config->view_tbar);
    if( display )
      display->ShowPanel(true);
    ShowFullScreen(false);
  }
  else {
    GetToolBar()->Show(false);
    if( display )
      display->ShowPanel(false);
    ShowFullScreen(true);
  }
}

void MuniView::OnTune(wxCommandEvent& event)
{
  wxASSERT(dynamic_cast<MuniDisplay *>(place));
  dynamic_cast<MuniDisplay *>(place)->ShowTune(event.IsChecked());
}

void MuniView::OnHeader(wxCommandEvent& event)
{
  MuniHeader *header = new MuniHeader(this,config);
  header->SetHdu(fits.Hdu(hdusel));
  header->Show();
}

void MuniView::OnAstrometry(wxCommandEvent& event)
{
  FitsTable table;

  for(size_t i = 0; i < fits.HduCount(); i++) {
    const FitsHdu h(fits.Hdu(i));

    if( h.IsOk() && h.GetExtname().Find("MUNIPACK") != wxNOT_FOUND ) {
      table = FitsTable(fits.Hdu(i));
      break;
    }
  }

  wxASSERT(dynamic_cast<MuniDisplay *>(place));
  dynamic_cast<MuniDisplay *>(place)->Astrometry(fits.GetFullPath(),table);
}

void MuniView::OnPhotometry(wxCommandEvent& event)
{
  wxASSERT(dynamic_cast<MuniDisplay *>(place));
  dynamic_cast<MuniDisplay *>(place)->ShowPhotometry(fits.GetFullPath());
}

void MuniView::OnCalibrate(wxCommandEvent& event)
{
  wxASSERT(dynamic_cast<MuniDisplay *>(place));
  dynamic_cast<MuniDisplay *>(place)->Calibrate(fits.GetFullPath());
}

void MuniView::OnShowGrid(wxCommandEvent& event)
{
  wxASSERT(dynamic_cast<MuniDisplay *>(place));
  dynamic_cast<MuniDisplay *>(place)->ShowGrid(event.IsChecked());
}

void MuniView::HelpGuide(wxCommandEvent& WXUNUSED(event))
{
  MuniHelp *h = new MuniHelp(config);
  h->Show();
}

void MuniView::HelpAbout(wxCommandEvent& WXUNUSED(event))
{
  MuniAbout();
}

void MuniView::HelpBug(wxCommandEvent& WXUNUSED(event))
{
  wxLaunchDefaultBrowser(BUGPAGE);
}

void MuniView::OnMenuStructure(wxCommandEvent& event)
{
  int hdu = -2;

  if( event.GetId() == wxID_HOME )
    hdu = -1;

  for(size_t i = 0; i < viewid.size(); i++)
    if( viewid[i] == event.GetId() )
      hdu = i;

  if( hdu == hdusel || hdu < -1 ) return;

  hdusel = hdu;

  HduSelect(hdusel);
  MenuSelect(hdusel);
  TreeSelect(hdusel);

}

void MuniView::OnResumeHdu(MuniHduEvent& event)
{
  wxLogDebug("HDU %d ",event.hdu);

  if( hdusel == event.hdu ) return;

  hdusel = event.hdu;

  MenuSelect(event.hdu);
  TreeSelect(event.hdu);
  HduSelect(event.hdu);
}

void MuniView::MenuSelect(int hdu)
{
  // set menu selector
  wxASSERT(menuWin);
  if( hdu >= 0 )
    menuWin->Check(viewid[hdu],true);
  else
    menuWin->Check(wxID_HOME,true);
}

void MuniView::TreeSelect(int hdu)
{
  MuniStructtree *tree = static_cast<MuniStructtree *>(structtree->GetPopupControl());
  wxASSERT(tree);
  tree->SetHdu(hdu);
  wxTreeItemId item = tree->GetSelection();
  structtree->SetValue(tree->GetItemText(item));
  structtree->Refresh();
}

void MuniView::HduSelect(int h)
{
  if( typeid(*place)==typeid(MuniSplashing) )
    DeleteSplashing();
  else if( typeid(*place)==typeid(MuniHead) )
    DeleteHead();
  else if( typeid(*place)==typeid(MuniResume) )
    DeleteResume();
  else if( typeid(*place)==typeid(MuniDisplay) )
    DeleteDisplay();
  else if( typeid(*place)==typeid(MuniGrid) )
    DeleteGrid();
  else
    wxFAIL_MSG("----- WARNING: Unreachable code.");

  hdusel = h;
  wxLogDebug("MuniView::HduSelect %d",hdusel);

  if( hdusel >= 0 ) {

    const FitsMetaHdu hdu = meta.Hdu(hdusel);
    
    switch(fits.Hdu(hdusel).Type()) {
    case HDU_IMAGE:  CreateDisplay();   break;
    case HDU_TABLE:  CreateGrid();      break;
    case HDU_HEAD:   CreateHead();      break;
    default:         CreateResume(); break;
    }

  }
  else
    CreateResume();
}

void MuniView::ReplacePlace(wxWindow *newin)
{
  wxWindow *p = place;
  place = newin;
  wxSizer *topsizer = GetSizer();
  topsizer->Replace(p,place);
  p->Destroy();
  Layout();
}

void MuniView::CreateSplashing()
{
  ReplacePlace(new MuniSplashing(this,config));
}

void MuniView::DeleteSplashing()
{
}

void MuniView::CreateResume()
{
  ReplacePlace(new MuniResume(this,config,meta));
}

void MuniView::DeleteResume()
{
}

void MuniView::CreateHead()
{
  ReplacePlace(new MuniHead(this,config));
  static_cast<MuniHead *>(place)->SetHdu(fits.Hdu(hdusel)); 
  GetToolBar()->EnableTool(wxID_HOME,true);
  GetToolBar()->EnableTool(ID_INFO,true);

  wxMenu *menuHead = new wxMenu;
  menuHead->Append(ID_INFO,"Header");
  GetMenuBar()->Insert(3,menuHead,MENU_HEAD);

  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnHeader,this,ID_INFO);
}

void MuniView::DeleteHead()
{
  GetToolBar()->EnableTool(wxID_HOME,false);
  GetToolBar()->EnableTool(ID_INFO,false);
  Unbind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnHeader,this,ID_INFO);
  wxMenuBar *menuBar = GetMenuBar();
  int id = menuBar->FindMenu(MENU_HEAD);
  if( id != wxNOT_FOUND ) {
    wxMenu *menuHead = menuBar->Remove(id);
    delete menuHead;
  }
}

void MuniView::CreateGrid()
{
  ReplacePlace(new MuniGrid(this,config));
  static_cast<MuniGrid *>(place)->SetHdu(fits.Hdu(hdusel));
  GetToolBar()->EnableTool(wxID_HOME,true);
  GetToolBar()->EnableTool(ID_INFO,true);

  wxMenu *menuGrid = new wxMenu;
  menuGrid->Append(ID_INFO,"Header");
  GetMenuBar()->Insert(3,menuGrid,MENU_GRID);

  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnHeader,this,ID_INFO);
}

void MuniView::DeleteGrid()
{
  GetToolBar()->EnableTool(wxID_HOME,false);
  GetToolBar()->EnableTool(ID_INFO,false);
  Unbind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnHeader,this,ID_INFO);

  wxMenuBar *menuBar = GetMenuBar();
  int id = menuBar->FindMenu(MENU_GRID);
  if( id != wxNOT_FOUND ) {
    wxMenu *menuGrid = menuBar->Remove(id);
    delete menuGrid;
  }
}

void MuniView::CreateDisplay()
{
  ReplacePlace(new MuniDisplay(this,config));
  static_cast<MuniDisplay *>(place)->SetHdu(fits.Hdu(hdusel),
					    meta.Hdu(hdusel).GetIcon()); 

  // menus
  menuView->AppendCheckItem(ID_DETAIL,"Detail Panel");
  menuView->Check(ID_DETAIL,true);

  wxMenu *menuProfile = new wxMenu;
  wxArrayString profiles = FitsItt::Type_str();
  for(size_t i = 0; i < profiles.GetCount(); i++) {
    menuProfile->AppendRadioItem(wxID_ANY,profiles.Item(i));
    long id = menuProfile->FindItem(profiles.Item(i));
    Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnMenuItt,this,id);
  }

  wxString label;
  label = FitsItt::Type_str(config->display_itt);
  int id = menuProfile->FindItem(label);
  wxASSERT(id != wxNOT_FOUND);
  menuProfile->Check(id,true);

  wxMenu *menuImage = new wxMenu;
  menuImage->Append(ID_INFO,"Header");
  //  menuImage->Append(ID_PHOT,"Photometry");
  //  menuImage->Append(ID_ASTROMETRY,"Astrometry");
  menuImage->Append(ID_CALIBR,"Calibrate");
  menuImage->AppendSeparator();
  menuImage->AppendCheckItem(ID_GRID,"Grid");

  wxMenu *menuTune = new wxMenu;
  menuTune->Append(wxID_ZOOM_FIT,"Best fit\tCtrl+*");
  menuTune->Append(wxID_ZOOM_100,"Normal size\tCtrl+0");
  menuTune->Append(wxID_ZOOM_IN,"Zoom\tCtrl++");
  menuTune->Append(wxID_ZOOM_OUT,"Shrink\tCtrl+-");
  menuTune->AppendSeparator();
  menuTune->Append(ID_CYCLE_ITT,"&Cycle Tones\tCtrl+T",
  		    "Cycle throughout Tone curves");
  menuTune->AppendSubMenu(menuProfile,"&" TONE_CURVES);
  menuTune->AppendSeparator();

  if( !fits.Hdu(hdusel).IsColor() ) {

    wxMenu *menuPalette = new wxMenu;
    wxArrayString pals = FitsPalette::Type_str();
    for(size_t i = 0; i < pals.GetCount(); i++) {
      menuPalette->AppendRadioItem(wxID_ANY,pals.Item(i));
      long id = menuPalette->FindItem(pals.Item(i));
      Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnMenuPal,this,id);
    }

    label = FitsPalette::Type_str(config->display_pal);
    id = menuPalette->FindItem(label);
    wxASSERT(id != wxNOT_FOUND);
    menuPalette->Check(id,true);

    menuTune->AppendCheckItem(ID_MENU_NEGATIVE,"Negative");
    menuTune->Append(ID_CYCLE_PAL,"&Cycle Palette\tCtrl+P",
		     "Cycle throughout Color Palettes");
    menuTune->AppendSubMenu(menuPalette,"&" COLOR_PALETTES);
    menuTune->AppendSeparator();
  }

  menuTune->Append(ID_TUNE,"&Tune...");

  wxMenu *menuTools = new wxMenu;
  menuTools->Append(ID_ASTROMETRY,"Astrometry...");

  GetMenuBar()->Insert(3,menuImage,MENU_IMAGE);
  GetMenuBar()->Insert(4,menuTune,MENU_TUNE);
  GetMenuBar()->Insert(5,menuTools,MENU_TOOLS);
  menuView->Enable(ID_FULLSCREEN, true);

  // toolbar
  MuniArtIcons ico(wxART_TOOLBAR,wxSize(22,22));
  
  wxToolBar *tbar = GetToolBar();
  wxASSERT(tbar);
  tbar->EnableTool(wxID_HOME, true);
  tbar->EnableTool(ID_INFO,true);

  // ntools must be set to number of really added tools
  ntools = 5;

  tbar->InsertTool(0,ID_TUNE,"Tune",ico.Icon("preferences-desktop"),wxNullBitmap,wxITEM_NORMAL,
		"Show window with fine tunning controls");
  tbar->InsertSeparator(1);
  tbar->InsertTool(2,wxID_ZOOM_100,"Original",ico.Icon("zoom-original"),wxNullBitmap,wxITEM_NORMAL,
		   "Original Size");
  tbar->InsertTool(3,wxID_ZOOM_FIT,"Fit",ico.Icon("zoom-fit-best"),wxNullBitmap,wxITEM_NORMAL,
		   "Fit to Size");
  tbar->InsertSeparator(4);

  wxASSERT(ntools - 1 == 4);

  tbar->Realize();

  tbar->EnableTool(ID_INFO,true);  

  // some stars
  for(size_t i = 0; i < fits.HduCount(); i++) {
    if( fits.Hdu(i).GetKey("EXTNAME").Find("MUNIPACK") != wxNOT_FOUND ) {
      FitsTable t(fits.Hdu(i));
      dynamic_cast<MuniDisplay *>(place)->SetStars(t);
      /*      
      wxMemoryOutputStream ostream;
      t.GetStarChart(ostream);
      
      wxMemoryInputStream istream(ostream);
      dynamic_cast<MuniDisplay *>(place)->SetOverlay(istream);
      */
    }
  }

  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnMenuZoom,this,wxID_ZOOM_100);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnMenuZoom,this,wxID_ZOOM_FIT);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnCycleZoom,this,wxID_ZOOM_IN);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnCycleZoom,this,wxID_ZOOM_OUT);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnMenuNegative,this,ID_MENU_NEGATIVE);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnCycleItt,this,ID_CYCLE_ITT);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnCyclePal,this,ID_CYCLE_PAL);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnTune,this,ID_TUNE);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnDetailPanel,this,ID_DETAIL);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnHeader,this,ID_INFO);
  //  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnPhotometry,this,ID_PHOT);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnAstrometry,this,ID_ASTROMETRY);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnCalibrate,this,ID_CALIBR);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnShowGrid,this,ID_GRID);
  Bind(EVT_TUNE,&MuniView::OnTuneFine,this);
  //  Bind(EVT_DIALOG,&MuniView::OnPhotometry,this,ID_PHOT);

}

void MuniView::DeleteDisplay()
{
  wxToolBar *tbar = GetToolBar();
  wxASSERT(tbar);
  for(int i = 0; i < ntools; i++)
    tbar->DeleteToolByPos(ntools - i - 1);

  tbar->Realize();

  tbar->EnableTool(ID_INFO,false);
  tbar->EnableTool(wxID_HOME,false);

  Unbind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnMenuZoom,this,wxID_ZOOM_100);
  Unbind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnMenuZoom,this,wxID_ZOOM_FIT);
  Unbind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnCycleZoom,this,wxID_ZOOM_IN);
  Unbind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnCycleZoom,this,wxID_ZOOM_OUT);
  Unbind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnMenuNegative,this,ID_MENU_NEGATIVE);
  Unbind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnCycleItt,this,ID_CYCLE_ITT);
  Unbind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnCyclePal,this,ID_CYCLE_PAL);
  Unbind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnTune,this,ID_TUNE);
  Unbind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnDetailPanel,this,ID_DETAIL);
  Unbind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnHeader,this,ID_INFO);
  //  Unbind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnPhotometry,this,ID_PHOT);
  Unbind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnAstrometry,this,ID_ASTROMETRY);
  Unbind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnCalibrate,this,ID_CALIBR);
  Unbind(wxEVT_COMMAND_MENU_SELECTED,&MuniView::OnShowGrid,this,ID_GRID);
  Unbind(EVT_TUNE,&MuniView::OnTuneFine,this);
  //  Unbind(EVT_DIALOG,&MuniView::OnPhotometry,this,ID_PHOT);

  wxMenuBar *menuBar = GetMenuBar();
  int id = menuBar->FindMenu(MENU_IMAGE);
  if( id != wxNOT_FOUND ) {
    wxMenu *menuImage = menuBar->Remove(id);
    delete menuImage;
  }
  id = menuBar->FindMenu(MENU_TUNE);
  if( id != wxNOT_FOUND ) {
    wxMenu *menuTune = menuBar->Remove(id);
    delete menuTune;
  }
  id = menuBar->FindMenu(MENU_TOOLS);
  if( id != wxNOT_FOUND ) {
    wxMenu *menuTools = menuBar->Remove(id);
    delete menuTools;
  }

  menuView->Delete(ID_DETAIL);
}

void MuniView::OnDetailPanel(wxCommandEvent& event)
{
  wxASSERT(dynamic_cast<MuniDisplay *>(place));
  dynamic_cast<MuniDisplay *>(place)->ShowPanel(event.IsChecked());
}


void MuniView::OnEnterWin(wxMouseEvent& event)
{
  wxLogDebug("MuniView::OnEnterWin");
  wxASSERT(place);
  place->SetFocus();
}

void MuniView::OnLeaveWin(wxMouseEvent& event)
{
  // ?????
}

void MuniView::OnSize(wxSizeEvent& event)
{
  //  wxASSERT(place);
  //Layout();

  // arrange stretch space in toolbar 
  //  wxSize size(GetSize());

  /*
  if( dynamic_cast<MuniDisplay*>(place) )
    place->SetSize(1,1);
  */
  // Black Magick. The part of code together with code in display.cpp
  // setups scrolling for images. I had spend lot of time with looking
  // for a way how correctly does it. The principle is set size
  // of place to minumum while deriving virtual size by image. 

  //  wxPostEvent(place,event);

  event.Skip();
}

void MuniView::Coloring(wxCommandEvent& WXUNUSED(event))
{
  if( coloring ) return;

  Bind(EVT_FILELOAD,&MuniView::OnColoringFinish,this);

  coloring = new MuniColoring(this,config);
  coloring->Show(true);
}

void MuniView::OnColoringFinish(wxCommandEvent& event)
{
  wxLogDebug("MuniView::OnColoringFinish");

  wxASSERT(coloring);
  coloring->Destroy();
  coloring = 0;

  Unbind(EVT_FILELOAD,&MuniView::OnColoringFinish,this);

  wxString file(event.GetString());
  if( ! file.IsEmpty() )
    LoadFile(file);
}

// void MuniView::OnPhotometry(MuniDialogEvent& event)
// {
//   wxLogDebug("Running OnPhotometry...");

//   result = event.results[0];

//   Bind(wxEVT_END_PROCESS,&MuniView::OnPhotometryFinish,this);

//   MuniProcess *action = new MuniProcess(&pipe,event.commands[0]);
//   pipe.push(action);

//   for(size_t i = 0; i < event.params.GetCount(); i++){
//     wxLogDebug(event.params[i]);
//     action->Write(event.params[i]);
//   }

//   pipe.Start();

// }

// void MuniView::OnPhotometryFinish(wxProcessEvent& event)
// {
//   wxLogDebug("MuniView::OnPhotometryFinish");

//   Unbind(wxEVT_END_PROCESS,&MuniView::OnPhotometryFinish,this);

//   if( event.GetExitCode() != 0 )
//     wxLogDebug("Failed with exit code %d",event.GetExitCode());
//   else
//     LoadFile(result);
// }

// void MuniView::RunShell(const queue<MuniCommander>& com)
// {
//   //  wxBeginBusyCursor(); // must be called out of exec and write to 

//   shell = new MuniShell(GetEventHandler(),com);

//   /*
//   menuView->Enable(wxID_STOP,true);
//   tbot->AddTool(tstop);
//   tbot->Realize();
//   archiveprop->SetLabel(wxT("Files are being processed."));
//   SetArchiveSize();
//   */
// } 

// void MuniView::OnShell(MuniShellEvent& event)
// {
//   wxLogDebug("MuniBrowser::OnShell");

//   if( event.finish ) {
//     /*
//     tstop = tbot->RemoveTool(wxID_STOP);
//     tbot->Realize();
//     menuView->Enable(wxID_STOP,false);
//     */
//     delete shell;
//     shell = 0;

//     //    wxEndBusyCursor();
//   }

//   /*
//   console->AppendOutput(event.out);
//   console->AppendError(event.err);
//   */

//   wxArrayString results = event.res; 
//   for(size_t i = 0; i < results.GetCount(); i++) {
//     wxLogDebug(results[i]);
//     LoadFile(results[i]);
//     /*
//     FitsFile fits(results[i]);
//     if( fits.Status() ) {
//       MuniIcon micon(fits,config);
//       FitsMeta meta(fits,micon.GetIcon(),micon.GetList());
//       if( meta.IsOk() ) {
// 	FitsOpenEvent ev(xEVT_FITS_OPEN,ID_MRENDER);
// 	ev.filename = results[i];
// 	ev.fits = fits;
// 	ev.meta = meta;
// 	wxPostEvent(list,ev);
//       }
//     }
//     */
//   }

// }

void MuniView::OnConfigUpdated(wxCommandEvent& event)
{
  /*
  MuniDisplay *d = GetDisplay();
  if( d )
    wxQueueEvent(d,event.Clone());
  */
}

// void MuniView::SetAstrometry()
// {
//   wxBusyCursor wait;

//   // create new temp
//   wxString b = wxFileName::CreateTempFileName("xmunipack-backup_");
//   wxRemoveFile(b);
//   wxCopyFile(fits.GetFullPath(),b);
//   backup.Add(b);

//   // update selected HDU in temp


//   // reload fits
//   LoadFile(b);
// }

wxString MuniView::CreateTempFileName()
{
  wxString b = wxFileName::CreateTempFileName("xmunipack-backup_");
  if( b.IsEmpty() ) {
    wxLogMessage("Failed to create a temp file.");
    return wxEmptyString;
  }
  b += ".fits";

  backup.Add(b);

  return b;
}


wxString MuniView::CreateTempHdu()
{
  wxBusyCursor wait;

  wxString b = wxFileName::CreateTempFileName("xmunipack-backup_");
  if( b.IsEmpty() ) {
    wxLogMessage("Failed to create a temp file.");
    return wxEmptyString;
  }

  b += ".fits";

  backup.Add(b);
  
  wxString orig(fits.GetFullPath());
  if( hdusel >= 0 ) {
    wxString ext;
    ext.Printf("[%d]",hdusel);
    orig += ext;
  }

  FitsCopyFile(orig,b);


  // // create new temp
  // wxFile temp;
  // wxString b = wxFileName::CreateTempFileName("xmunipack-backup_",&temp);
  // if( temp.IsOpened() ) {
  //   const int BUFSIZE = 2880;
  //   char buf[BUFSIZE];
    
  //   wxFile orig(fits.GetFullPath());
  //   if( orig.IsOpened() ) {
  //     while( ! orig.Eof() ) {
  // 	orig.Read(&buf,BUFSIZE);
  // 	temp.Write(buf,BUFSIZE);
  //     }
  //   }
  // }
  

  // /*
  // wxRemoveFile(b);
  // wxCopyFile(fits.GetFullPath(),b);
  // */
  // backup.Add(b);
  
  // if( hdusel >= 0 ) {
  //   wxString ext;
  //   ext.Printf("[%d]",hdusel);
  //   b += ext;
  // }

  return b;
}

void MuniView::UnlinkBackup()
{
  wxString b(backup.Last());
  if( ! b.IsEmpty() ) {
    wxRemoveFile(b);
    backup.Remove(b);
  }
}


// --- FitsOpen -------------------

FitsOpen::FitsOpen(wxEvtHandler *h, const wxString& fn):
wxThread(wxTHREAD_DETACHED),filename(fn),handler(h)
{
  wxASSERT(handler);
}

FitsOpen::~FitsOpen()
{
  wxCriticalSectionLocker enter(static_cast<MuniView *>(handler)->loaderCS);
  static_cast<MuniView *>(handler)->loader = 0;
}

wxThread::ExitCode FitsOpen::Entry()
{
  FitsFile fits(filename);

  FitsOpenEvent ev(EVT_FITS_OPEN,ID_LOADER);
  ev.filename = filename;
  ev.fits = fits;
  wxQueueEvent(handler,ev.Clone());

  return (wxThread::ExitCode) 0;
}
